﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace HlgSiemensCommunication
{
    public class SiemensCommand
    {
        /// <summary>
        /// 拼接命令
        /// </summary>
        /// <returns></returns>
        public byte[] GetReadCommand(List<InputSiemensParams> siemens, out List<SiemensParams> outsiemensParams)
        {
            var virtualsiemens = InitDBVirtual(siemens);
            outsiemensParams = virtualsiemens;

            byte[] resultCommand = new byte[19 + (12 * virtualsiemens.Count)];

            //TPKT 4位
            {
                resultCommand[0] = 0x03;
                resultCommand[1] = 0x00;

                //2-3 整体报文长度
                resultCommand[2] = BitConverter.GetBytes(resultCommand.Length)[1];
                resultCommand[3] = BitConverter.GetBytes(resultCommand.Length)[0];
            }

            //ISO 3位
            {
                resultCommand[4] = 0x02;
                resultCommand[5] = 0xF0;
                resultCommand[6] = 0x80;
            }

            //header  10位
            {
                resultCommand[7] = 0x32;
                resultCommand[8] = 0x01;
                resultCommand[9] = 0x00;
                resultCommand[10] = 0x00;
                resultCommand[11] = 0x00;
                resultCommand[12] = 0x01;
                //13-14 读取的param长度
                resultCommand[13] = BitConverter.GetBytes((12 * virtualsiemens.Count) + 2)[1];
                resultCommand[14] = BitConverter.GetBytes((12 * virtualsiemens.Count) + 2)[0];

                resultCommand[15] = 0x00;
                resultCommand[16] = 0x00;
            }

            //param  2位
            {
                //17(读代码) 
                resultCommand[17] = 0x04;
                //18(总共读取个数) 
                resultCommand[18] = (byte)virtualsiemens.Count;
            }

            //param 组装读取参数 12
            {
                for (int i = 0; i < virtualsiemens.Count; i++)
                {
                    var itemNo = i * 12;
                    resultCommand[19 + itemNo] = 0x12;
                    resultCommand[20 + itemNo] = 0x0a;
                    resultCommand[21 + itemNo] = 0x10;

                    resultCommand[22 + itemNo] = (virtualsiemens[i].ItemType == SiemensDataType.Bool) ? (byte)(0x01) : (byte)0x02;
                    //(数据长度)
                    resultCommand[23 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].Length)[1];
                    resultCommand[24 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].Length)[0];
                    ////DB地址
                    //resultCommand[25 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].VirtaulDB)[1];
                    //resultCommand[26 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].VirtaulDB)[0];
                    ////DB块代码)
                    //resultCommand[27 + itemNo] = 0x84;
                    //DB地址
                    if (virtualsiemens[i].DBtype == DBtype.DB)
                    {
                        resultCommand[25 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].VirtaulDB)[1];
                        resultCommand[26 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].VirtaulDB)[0];
                    }
                    else
                    {
                        resultCommand[25 + itemNo] = BitConverter.GetBytes(0)[1];
                        resultCommand[26 + itemNo] = BitConverter.GetBytes(0)[0];
                    }

                    //DB块代码)
                    switch (virtualsiemens[i].DBtype)
                    {
                        case DBtype.DB:
                            resultCommand[27 + itemNo] = 0x84;
                            break;
                        case DBtype.I:
                            resultCommand[27 + itemNo] = 0x81;
                            break;
                        case DBtype.Q:
                            resultCommand[27 + itemNo] = 0x82;
                            break;
                        case DBtype.M:
                            resultCommand[27 + itemNo] = 0x83;
                            break;
                    }
                    //(偏移地址)
                    resultCommand[28 + itemNo] = (byte)(virtualsiemens[i].VirtualOffSet / 256 / 256 % 256);
                    resultCommand[29 + itemNo] = (byte)(virtualsiemens[i].VirtualOffSet / 256 % 256);
                    resultCommand[30 + itemNo] = (byte)(virtualsiemens[i].VirtualOffSet % 256);
                }
            }

            return resultCommand;
        }

        /// <summary>
        /// 拼接命令
        /// </summary>
        /// <returns></returns>
        public byte[] GetWriteCommand(List<InputSiemensParams> siemens, out List<SiemensParams> outSiemensParams)
        {
            var virtualsiemens = InitDBVirtual(siemens);
            outSiemensParams = virtualsiemens;

            //填充字节
            var fullbyte = virtualsiemens.Sum(x => x.FullByte);
            //总长度
            var DataLength = virtualsiemens.Sum(X => X.WriteLength) + fullbyte;

            byte[] resultCommand = new byte[19 + (12 * virtualsiemens.Count) + DataLength];
            //TPKT 4位
            {
                resultCommand[0] = 0x03;
                resultCommand[1] = 0x00;

                //2-3 整体报文长度
                resultCommand[2] = BitConverter.GetBytes(resultCommand.Length)[1];
                resultCommand[3] = BitConverter.GetBytes(resultCommand.Length)[0];
            }

            //ISO 3位
            {
                resultCommand[4] = 0x02;
                resultCommand[5] = 0xF0;
                resultCommand[6] = 0x80;
            }

            //header  10位
            {
                resultCommand[7] = 0x32;
                resultCommand[8] = 0x01;
                resultCommand[9] = 0x00;
                resultCommand[10] = 0x00;
                resultCommand[11] = 0x00;
                resultCommand[12] = 0x01;
                //13-14 读取的param长度
                resultCommand[13] = BitConverter.GetBytes((12 * virtualsiemens.Count) + 2)[1];
                resultCommand[14] = BitConverter.GetBytes((12 * virtualsiemens.Count) + 2)[0];

                //写入的数据长度
                resultCommand[15] = BitConverter.GetBytes(DataLength)[1];
                resultCommand[16] = BitConverter.GetBytes(DataLength)[0];
            }

            //param  2位
            {
                //17(读代码) 
                resultCommand[17] = 0x05;
                //18(总共读取个数) 
                resultCommand[18] = (byte)virtualsiemens.Count;
            }

            //param 组装读取参数 12
            {
                for (int i = 0; i < siemens.Count; i++)
                {
                    var itemNo = i * 12;
                    resultCommand[19 + itemNo] = 0x12;
                    resultCommand[20 + itemNo] = 0x0a;
                    resultCommand[21 + itemNo] = 0x10;

                    resultCommand[22 + itemNo] = (virtualsiemens[i].ItemType == SiemensDataType.Bool) ? (byte)(0x01) : (byte)0x02;
                    //(数据长度)
                    resultCommand[23 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].Length)[1];
                    resultCommand[24 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].Length)[0];
                    //DB地址
                    if (virtualsiemens[i].DBtype == DBtype.DB)
                    {
                        resultCommand[25 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].VirtaulDB)[1];
                        resultCommand[26 + itemNo] = BitConverter.GetBytes(virtualsiemens[i].VirtaulDB)[0];
                    }
                    else
                    {
                        resultCommand[25 + itemNo] = BitConverter.GetBytes(0)[1];
                        resultCommand[26 + itemNo] = BitConverter.GetBytes(0)[0];
                    }

                    //DB块代码)
                    switch (virtualsiemens[i].DBtype)
                    {
                        case DBtype.DB:
                            resultCommand[27 + itemNo] = 0x84;
                            break;
                        case DBtype.I:
                            resultCommand[27 + itemNo] = 0x81;
                            break;
                        case DBtype.Q:
                            resultCommand[27 + itemNo] = 0x82;
                            break;
                        case DBtype.M:
                            resultCommand[27 + itemNo] = 0x83;
                            break;
                    }

                    //(偏移地址)
                    resultCommand[28 + itemNo] = (byte)(virtualsiemens[i].VirtualOffSet / 256 / 256 % 256);
                    resultCommand[29 + itemNo] = (byte)(virtualsiemens[i].VirtualOffSet / 256 % 256);
                    resultCommand[30 + itemNo] = (byte)(virtualsiemens[i].VirtualOffSet % 256);
                }
            }

            //data
            {
                var writestartLength = resultCommand.Length - DataLength;

                for (int i = 0; i < siemens.Count; i++)
                {
                    try
                    {
                        //填充字节 Full Byte
                        if (i > 0 && outSiemensParams[i - 1].ItemType == SiemensDataType.Bool)
                        {
                            resultCommand[writestartLength] = 0x00;//末尾
                            writestartLength += 1;
                        }

                        resultCommand[writestartLength] = 0x00; //默认首位

                        var coefficient = 0;
                        if (outSiemensParams[i].ItemType == SiemensDataType.Bool)
                        {
                            resultCommand[writestartLength + 1] = 0x03;//默认第二位  位
                            coefficient = 1;
                        }
                        else
                        {
                            resultCommand[writestartLength + 1] = 0x04;//默认第二位  字节
                            coefficient = 8;
                        }


                        //写入长度偏移
                        resultCommand[writestartLength + 2] = (byte)(outSiemensParams[i].Length * coefficient / 256);//默认第三位
                        resultCommand[writestartLength + 3] = (byte)(outSiemensParams[i].Length * coefficient % 256);//默认第四位


                        switch (siemens[i].ItemType)
                        {
                            case SiemensDataType.Bool:
                                resultCommand[writestartLength + 4] = (byte)Convert.ToInt16(siemens[i].WriteValue);
                                writestartLength += 5;
                                break;
                            case SiemensDataType.Float:
                                var ofloat = BitConverter.GetBytes(Convert.ToSingle(siemens[i].WriteValue));
                                resultCommand[writestartLength + 4] = ofloat[3];
                                resultCommand[writestartLength + 5] = ofloat[2];
                                resultCommand[writestartLength + 6] = ofloat[1];
                                resultCommand[writestartLength + 7] = ofloat[0];
                                writestartLength += 8;
                                break;
                            case SiemensDataType.String:
                                var inputstr = siemens[i].WriteValue?.ToString()?.Length;
                                if (inputstr == null)
                                    for (int isl = 0; isl < siemens[i].Length; isl++)
                                        resultCommand[writestartLength + 4 + isl] = 0x00;
                                else
                                {
                                    var strLength = System.Text.Encoding.Default.GetBytes(siemens[i].WriteValue.ToString()).Length;
                                    for (int ws = 0; ws < strLength; ws++)
                                        resultCommand[writestartLength + 4 + ws] = System.Text.Encoding.Default.GetBytes(siemens[i].WriteValue.ToString())[ws];

                                    var wleng = siemens[i].Length - strLength;
                                    if (wleng > 0)
                                        for (int wle = 0; wle < wleng; wle++)
                                            resultCommand[writestartLength + 4 + strLength + wle] = 0x00;
                                }

                                writestartLength += siemens[i].Length + 4;
                                break;
                            case SiemensDataType.Uint16:
                                var oushart = BitConverter.GetBytes(Convert.ToInt16(siemens[i].WriteValue));
                                resultCommand[writestartLength + 4] = oushart[1];
                                resultCommand[writestartLength + 5] = oushart[0];
                                writestartLength += 6;
                                break;
                            case SiemensDataType.Int16:
                                var oshart = BitConverter.GetBytes(Convert.ToInt16(siemens[i].WriteValue));
                                resultCommand[writestartLength + 5] = oshart[0];
                                resultCommand[writestartLength + 4] = oshart[1];
                                writestartLength += 6;
                                break;
                            case SiemensDataType.Byte:
                                resultCommand[writestartLength + 4] = byte.Parse(siemens[i].WriteValue.ToString());
                                resultCommand[writestartLength + 5] = 0;
                                resultCommand[writestartLength + 6] = 0;
                                resultCommand[writestartLength + 7] = 0;
                                writestartLength += 8;
                                break;
                            case SiemensDataType.StringArray:
                                resultCommand[writestartLength + 4] = byte.Parse(siemens[i].Length.ToString());
                                var inputstrArray = siemens[i].WriteValue?.ToString()?.Length;
                                if (inputstrArray == null)
                                {
                                    resultCommand[writestartLength + 4 + 1] = 0x00;
                                    for (int isl = 0; isl < siemens[i].Length - 2; isl++)
                                        resultCommand[writestartLength + 4 + 2 + isl] = 0x00;
                                }
                                else
                                {
                                    var inputarrayLength = System.Text.Encoding.Default.GetBytes(siemens[i].WriteValue.ToString()).Length;
                                    resultCommand[writestartLength + 4 + 1] = (byte)inputarrayLength;

                                    for (int ws = 0; ws < inputarrayLength; ws++)
                                        resultCommand[writestartLength + 4 + 2 + ws] = System.Text.Encoding.Default.GetBytes(siemens[i].WriteValue.ToString())[ws];

                                    var wleng = siemens[i].Length - inputarrayLength;
                                    if (wleng > 0)
                                        for (int wle = 0; wle < wleng; wle++)
                                            resultCommand[writestartLength + 4 + 2 + inputarrayLength + wle] = (byte)(char)32;
                                }

                                writestartLength += siemens[i].Length + 4;
                                break;
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            return resultCommand;
        }

        /// <summary>
        /// 初始化时映射
        /// </summary>
        /// <param name="initSiemensParams"></param>
        List<SiemensParams> InitDBVirtual(List<InputSiemensParams> initSiemensParams)
        {
            List<SiemensParams> siemensParams = new List<SiemensParams>();
            var virtualInitParams = initSiemensParams.Select(x => MapTo<InputSiemensParams, SiemensParams>(x)).ToList();

            for (int i = 0; i < virtualInitParams.Count; i++)
            {
                var x = virtualInitParams[i];
                var itemads = x.DB.Split('.');
                var db = itemads[0];
                var address = itemads[1].Substring(3, itemads[1].Length - 3);
                x.VirtaulDB = x.DBtype == DBtype.DB ? Convert.ToInt32(db.Substring(2, db.Length - 2)) : 0;
                x.VirtualOffSet = GetBeingAddress(address + "." + ((itemads.Length > 2) ? itemads[2] : "0"));

                switch (x.ItemType)
                {
                    case SiemensDataType.Int16:
                        x.ItemType = SiemensDataType.Int16;
                        x.Length = 2;
                        x.WriteLength = 6;
                        break;
                    case SiemensDataType.Bool:
                        x.ItemType = SiemensDataType.Bool;
                        x.Length = 1;
                        x.WriteLength = 5;
                        break;
                    case SiemensDataType.Float:
                        x.ItemType = SiemensDataType.Float;
                        x.Length = 4;
                        x.WriteLength = 8;
                        break;
                    case SiemensDataType.String:
                        x.ItemType = SiemensDataType.String;
                        if (!string.IsNullOrEmpty(x.Result?.ToString()))
                        {
                            if (x.Result.ToString().Length > x.Length)
                                x.Result = x.Result.ToString().Substring(0, x.Length);
                        }
                        x.WriteLength = x.Length + 4;
                        break;
                    case SiemensDataType.Uint16:
                        x.ItemType = SiemensDataType.Uint16;
                        x.Length = 2;
                        x.WriteLength = 6;
                        break;
                    case SiemensDataType.Byte:
                        x.ItemType = SiemensDataType.Byte;
                        x.Length = 4;
                        x.WriteLength = 8;
                        break;
                    case SiemensDataType.StringArray:
                        x.ItemType = SiemensDataType.StringArray;
                        if (!string.IsNullOrEmpty(x.Result?.ToString()))
                        {
                            if (x.Result.ToString().Length > x.Length)
                                x.Result = x.Result.ToString().Substring(0, x.Length);
                        }
                        x.WriteLength = x.Length + 4;
                        break;
                }


                if (i > 0 && virtualInitParams[i - 1].ItemType == SiemensDataType.Bool)
                    x.FullByte = 1;
            }

            siemensParams.AddRange(virtualInitParams);
            return siemensParams;
        }

        static int GetBeingAddress(string address)
        {
            if (address.IndexOf('.') < 0)
            {
                return int.Parse(address) * 8;
            }

            string[] array = address.Split('.');
            return Convert.ToInt32(array[0]) * 8 + Convert.ToInt32(array[1]);
        }


        /// <summary>
        /// 实体Dto映射
        /// </summary>
        /// <typeparam name="TIn">输入</typeparam>
        /// <typeparam name="TOut">输出</typeparam>
        /// <param name="tin"></param>
        /// <returns></returns>
        public static TOut MapTo<TIn, TOut>(TIn tin)
        {
            var type = Activator.CreateInstance<TOut>();
            var pName = tin.GetType();

            foreach (var item in type.GetType().GetProperties())
            {
                var ItemArray = pName.GetProperty(item.Name);

                if (ItemArray != null)
                {
                    item.SetValue(type, ItemArray.GetValue(tin, null), null);
                }
            }
            return type;
        }

        public string GetPLCString(string inputStr)
        {
            try
            {
                if (inputStr == null || inputStr.Length == 0)
                    return string.Empty;

                return GetPLCByte(System.Text.Encoding.Default.GetBytes(inputStr));
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public string GetPLCByte(byte[] inputStr, Encoding defaultASCII = null)
        {
            try
            {
                if (inputStr == null || inputStr.Length == 0)
                    return string.Empty;

                var outputByte = string.Empty;
                var newinputStr = inputStr;
                var outputList = newinputStr.ToList();

                outputList.RemoveAll(x => x.Equals(0));

                if (defaultASCII == null)
                    outputByte = System.Text.Encoding.ASCII.GetString(outputList.ToArray()).Trim();
                else
                    outputByte = defaultASCII.GetString(outputList.ToArray()).Trim();

                return outputByte;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public static void SafeClose(Socket socket)
        {
            try
            {
                if (socket?.Connected ?? false)
                    socket?.Shutdown(SocketShutdown.Both);
            }
            catch { }

            try
            {
                socket?.Close();
            }
            catch { }
        }
    }
}
